﻿// Sections of this code have been abridged from https://github.com/mono/mono/blob/master/mcs/class/corlib/System/TermInfoReader.cs under the MIT license

using System;
using System.IO;
using System.Linq;
using System.Text;

namespace BepInEx.Unix
{
	internal class TtyInfo
	{
		public string TerminalType { get; set; } = "default";

		public int MaxColors { get; set; }

		public string[] ForegroundColorStrings { get; set; }

		public static TtyInfo Default { get; } = new TtyInfo
		{
			MaxColors = 0
		};

		public string GetAnsiCode(ConsoleColor color)
		{
			if (MaxColors <= 0 || ForegroundColorStrings == null)
				return string.Empty;

			int index = (int)color % MaxColors;
			return ForegroundColorStrings[index];
		}
	}

	internal static class TtyHandler
	{
		private static readonly string[] ncursesLocations = {
			"/usr/share/terminfo",
			"/etc/terminfo",
			"/usr/lib/terminfo",
			"/lib/terminfo"
		};

		private static string TryTermInfoDir(string dir, string term)
		{
			string infoFilePath = $"{dir}/{(int)term[0]:x}/{term}";

			if (File.Exists(infoFilePath))
				return infoFilePath;

			infoFilePath = Utility.CombinePaths(dir, term.Substring(0, 1), term);

			if (File.Exists(infoFilePath))
				return infoFilePath;

			return null;
		}

		private static string FindTermInfoPath(string term)
		{
			if (string.IsNullOrEmpty(term))
				return null;

			string termInfoVar = Environment.GetEnvironmentVariable("TERMINFO");
			if (termInfoVar != null && Directory.Exists(termInfoVar))
			{
				string text = TryTermInfoDir(termInfoVar, term);
				if (text != null)
				{
					return text;
				}
			}

			foreach (string location in ncursesLocations)
			{
				if (Directory.Exists(location))
				{
					string text = TryTermInfoDir(location, term);

					if (text != null)
						return text;
				}
			}

			return null;
		}

		public static TtyInfo GetTtyInfo(string terminal = null)
		{
			terminal = terminal ?? Environment.GetEnvironmentVariable("TERM");
			var path = FindTermInfoPath(terminal);

			if (path == null)
				return TtyInfo.Default;

			byte[] buffer = File.ReadAllBytes(path);

			var info = TtyInfoParser.Parse(buffer);
			info.TerminalType = terminal;

			return info;
		}
	}

	internal static class TtyInfoParser
	{
		private static readonly int[] ansiColorMapping =
		{
			0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15
		};

		public static TtyInfo Parse(byte[] buffer)
		{
			int intSize;


			int magic = GetInt16(buffer, 0);

			switch (magic)
			{
				case 0x11a:
					intSize = 2;
					break;

				case 0x21E:
					intSize = 4;
					break;

				default:
					// Unknown ttyinfo format
					return TtyInfo.Default;
			}

			int boolFieldLength = GetInt16(buffer, 4);
			int intFieldLength = GetInt16(buffer, 6);
			int strOffsetFieldLength = GetInt16(buffer, 8);

			// Normally i'd put a more complete implementation here, but I only need to parse this info to get the max color count
			// Feel free to implement the rest of this using these sources:
			// https://github.com/mono/mono/blob/master/mcs/class/corlib/System/TermInfoReader.cs
			// https://invisible-island.net/ncurses/man/term.5.html
			// https://invisible-island.net/ncurses/man/terminfo.5.html

			int baseOffset = 12 + GetString(buffer, 12).Length + 1; // Skip the terminal name
			baseOffset += boolFieldLength; // Length of bool field section
			baseOffset += baseOffset % 2; // Correct for boundary

			int colorOffset =
				baseOffset
				+ (intSize * (int)TermInfoNumbers.MaxColors); // Finally the offset for the max color integer

			//int stringOffset = baseOffset + (intSize * intFieldLength);

			//int foregoundColorOffset =
			//	stringOffset
			//	+ (2 * (int)TermInfoStrings.SetAForeground);

			//foregoundColorOffset = stringOffset
			//					   + (2 * strOffsetFieldLength)
			//					   + GetInt16(buffer, foregoundColorOffset);

			var info = new TtyInfo();

			info.MaxColors = GetInteger(intSize, buffer, colorOffset);

			//string setForegroundTemplate = GetString(buffer, foregoundColorOffset);

			//info.ForegroundColorStrings = ansiColorMapping.Select(x => setForegroundTemplate.Replace("%p1%", x.ToString())).ToArray();
			info.ForegroundColorStrings = ansiColorMapping.Select(x => $"\u001B[{(x > 7 ? 82 + x : 30 + x)}m").ToArray();

			return info;
		}

		private static int GetInt32(byte[] buffer, int offset)
		{
			return buffer[offset]
				   | (buffer[offset + 1] << 8)
				   | (buffer[offset + 2] << 16)
				   | (buffer[offset + 3] << 24);
		}

		private static short GetInt16(byte[] buffer, int offset)
		{
			return (short)(buffer[offset]
						   | (buffer[offset + 1] << 8));
		}

		private static int GetInteger(int intSize, byte[] buffer, int offset)
		{
			return intSize == 2
				? GetInt16(buffer, offset)
				: GetInt32(buffer, offset);
		}

		private static string GetString(byte[] buffer, int offset)
		{
			int length = 0;

			while (buffer[offset + length] != 0x00)
				length++;

			return Encoding.ASCII.GetString(buffer, offset, length);
		}

		internal enum TermInfoNumbers
		{
			MaxColors = 13
		}

		internal enum TermInfoStrings
		{
			SetAForeground = 359
		}
	}
}